08. Thread Pools and Executors

Thread Pools and Executors

In this section, you'll learn why and how we use thread pools in Java.

ND079 JPND C2 L05 A07 Thread Pools And Executors

What is a Thread Pool?

A thread pool is a collection of threads that is used to efficiently execute and manage asynchronous work.

**Thread Pool**

Thread Pool

Thread pools reduce the cost of using threads by storing them in a worker thread pool. That way, your program can reuse existing threads instead of creating a new thread for each piece of work that needs to be done.

New work is added to a work queue, where it waits for a pooled thread to become available. When a thread is available, it will remove work from the queue, and do the work.

Benefits of Thread Pools

Thread pools have several advantages over creating and using Thread objects directly:

  • Limits the number of threads used by the program, and prevents the number of threads from growing in an unbounded manner.

  • Reuses worker threads, which reduces the time and memory spent creating new threads.

What can happen if your program does not limit the number of threads it creates, and ends up creating too many threads?

SOLUTION:
  • The program can slow to a crawl because it is trying to do too much work at once.
  • The program can run out of memory and crash.
  • The program may end up preventing other programs on the computer from creating operating system threads.

Creating Thread Pools

In Java, thread pools are created using the Executors API. Here are some examples:

  • A thread pool with only one thread:

    ExecutorService pool = Executors.newSingleThreadExecutor();

  • A thread pool that reuses threads but does not limit the number of threads it can create:

    ExecutorService pool = Executors.newCachedThreadPool();

  • A thread pool that reuses threads and limits the number of threads to 12:

    ExecutorService pool = Executors.newFixedThreadPool(12);

Why is it important to use caching thread pools?

SOLUTION: If a worker thread is idle, the thread pool can reuse it instead of creating a brand new thread.

Submitting Asynchronous Work

Thread pools have several methods that let you submit work to be executed asynchronously:

  • Submits a Runnable with no return value, and returns a Future:

    Future print = pool.submit(() -> System.out.println("foo"));

  • Submits a Runnable and returns void:

    pool.execute(() -> System.out.println("foo"));

  • Submits a Callable, whose return value will be accessible via the Future:

    Future pathFuture = pool.submit(() -> downloadFile());

Futures

ND079 JPND C2 L05 A08 Futures

A future is a reference to the result of an asynchronous computation.

Java uses the Future class to represent this concept.

If the asynchronous computation is done, calling the get() method will return the result of the computation. If the computation is not done, calling get() will cause the program to stop and wait for the computation to finish.

Futures are parameterized. Calling the get() method of a Future<Map> will return a Map. Or, if the result is a List, calling get() on a Future<List> will return a List result, and so on.

What is the maximum number of threads that this code can possibly run in parallel?

ExecutorService pool = Executors.newFixedThreadPool(12);
Future<?> a = pool.submit(() -> System.out.println("a"));
Future<?> b = pool.submit(() -> System.out.println("b"));
a.get();
Future<?> c = pool.submit(() -> System.out.println("c"));
SOLUTION: 2 threads

Joining Asynchronous Work

You learned that calling Future.get() on a future returned from a thread pool will cause the program to stop and wait for the parallel thread to finish its computation. This process of waiting for asynchronous work is called joining. That's why the Thread class has a method called join().

What happens if we don't have a Thread or Future to explicitly join? Here's one solution to the problem:

CountDownLatch latch = new CountDownLatch(1);
pool.execute(() -> {
  System.out.println("foo");
  latch.countDown();
});
latch.await();

In this code, CountDownLatch helps wait for the asynchronous work to complete even though we don't have a Thread or Future.